/**
 * 
 */
package qy.jalgotrade.broker;

import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.math.DoubleMath;

import qy.jalgotrade.utils.CommonUtils;

/**
 * <pre>
 * Orders
 * http://stocks.about.com/od/tradingbasics/a/markords.htm
 * http://www.interactivebrokers.com/en/software/tws/usersguidebook/ordertypes/basic_order_types.htm
 * 
 * State chart:
 * INITIAL           -> SUBMITTED
 * INITIAL           -> CANCELED
 * SUBMITTED         -> ACCEPTED
 * SUBMITTED         -> CANCELED
 * ACCEPTED          -> FILLED
 * ACCEPTED          -> PARTIALLY_FILLED
 * ACCEPTED          -> CANCELED
 * PARTIALLY_FILLED  -> PARTIALLY_FILLED
 * PARTIALLY_FILLED  -> FILLED
 * PARTIALLY_FILLED  -> CANCELED
 * </pre>
 * 
 * <pre>
 * Base class for orders.
 * 
 * .. note:: This is a base class and should not be used directly.
 * 
 *     Valid **type** parameter values are:
 * 
 *         Order.Type.MARKET
 *         Order.Type.LIMIT
 *         Order.Type.STOP
 *         Order.Type.STOP_LIMIT
 * 
 *     Valid **action** parameter values are:
 * 
 *         Order.Action.BUY
 *         Order.Action.BUY_TO_COVER
 *         Order.Action.SELL
 *         Order.Action.SELL_SHORT
 * </pre>
 * 
 * @author c-geo
 *
 */
public class Order {

	public enum Action {

		// @formatter:off
		BUY(1),
		BUY_TO_COVER(2),
		SELL(3),
		SELL_SHORT(4);

		private int value;

		private Action(int value) {

			this.value = value;
		}

		public int getValue() {

			return value;
		}
	}

	public enum State {

		INITIAL(1), // Initial state.
		SUBMITTED(2), // Order has been submitted.
		ACCEPTED(3), // Order has been acknowledged by the broker.
		CANCELED(4), // Order has been canceled.
		PARTIALLY_FILLED(5), // Order has been partially filled.
		FILLED(6); // Order has been completely filled.

		private int value;

		private State(int value) {

			this.value = value;
		}

		public int getValue() {

			return value;
		}
	}

	public enum Type {

		MARKET(1),
		LIMIT(2),
		STOP(3),
		STOP_LIMIT(4),
		NEXT_CUSTOM_TYPE(1000);

		private int value;

		private Type(int value) {

			this.value = value;
		}

		public int getValue() {

			return value;
		}
	}

	/**
	 * Valid state transitions.
	 */
	public static Map<State, List<State>> VALID_TRANSITIONS = new HashMap<>();

	static {

		VALID_TRANSITIONS.put(State.INITIAL, Arrays.asList(State.SUBMITTED, State.CANCELED));
		VALID_TRANSITIONS.put(State.SUBMITTED, Arrays.asList(State.ACCEPTED, State.CANCELED));
		VALID_TRANSITIONS.put(State.ACCEPTED, Arrays.asList(State.PARTIALLY_FILLED, State.FILLED, State.CANCELED));
		VALID_TRANSITIONS.put(State.PARTIALLY_FILLED, Arrays.asList(State.PARTIALLY_FILLED, State.FILLED, State.CANCELED));
		// @formatter:on
	}

	private String __id;

	private Type __type;

	private Action __action;

	private String __instrument;

	private double __quantity;

	private InstrumentTraits __instrumentTraits;

	private double __filled;

	private double __avgFillPrice;

	private OrderExecutionInfo __executionInfo;

	private boolean __goodTillCanceled;

	private double __commissions;

	private boolean __allOrNone;

	private State __state;

	private ZonedDateTime __submitDateTime;

	/**
	 * 
	 * @param type             The order type
	 * @param action           The order action.
	 * @param instrument       Instrument identifier.
	 * @param quantity         Order quantity.
	 * @param instrumentTraits
	 * @throws Exception
	 */
	public Order(Type type, Action action, String instrument, double quantity, InstrumentTraits instrumentTraits)
	        throws Exception {

		if (quantity <= 0) {
			throw new Exception("Invalid quantity");
		}

		__id = null;
		__type = type;
		__action = action;
		__instrument = instrument;
		__quantity = quantity;
		__instrumentTraits = instrumentTraits;
		__filled = 0;
		__avgFillPrice = Double.NaN;
		__executionInfo = null;
		__goodTillCanceled = false;
		__commissions = 0;
		__allOrNone = false;
		__state = State.INITIAL;
		__submitDateTime = null;
	}

	protected void _setQuantity(double quantity) {

		assert quantity > 0 : "Invalid quantity";
		__quantity = quantity;
	}

	/**
	 * <pre>
	 * Returns the order id.
	 * 
	 * .. note::
	 * 
	 *     This will be None if the order was not submitted.
	 * </pre>
	 * 
	 * @return
	 */
	public String getId() {

		return __id;
	}

	public InstrumentTraits getInstrumentTraits() {

		return __instrumentTraits;
	}

	/**
	 * <pre>
	 * Returns the order type. Valid order types are:
	 * 
	 *     * Order.Type.MARKET
	 *     * Order.Type.LIMIT
	 *     * Order.Type.STOP
	 *     * Order.Type.STOP_LIMIT
	 * </pre>
	 * 
	 * @return
	 */
	public Type getType() {

		return __type;
	}

	/**
	 * Returns the datetime when the order was submitted.
	 * 
	 * @return
	 */
	public ZonedDateTime getSubmitDateTime() {

		return __submitDateTime;
	}

	public void setSubmitted(String orderId, ZonedDateTime dateTime) {

		assert __id == null || orderId.equals(__id);
		__id = orderId;
		__submitDateTime = dateTime;
	}

	/**
	 * <pre>
	 * Returns the order action. Valid order actions are:
	 * 
	 *     * Order.Action.BUY
	 *     * Order.Action.BUY_TO_COVER
	 *     * Order.Action.SELL
	 *     * Order.Action.SELL_SHORT
	 * </pre>
	 * 
	 * @return
	 */
	public Action getAction() {

		return __action;
	}

	/**
	 * <pre>
	 * Returns the order state. Valid order states are:
	 * 
	 *     * Order.State.INITIAL (the initial state).
	 *     * Order.State.SUBMITTED
	 *     * Order.State.ACCEPTED
	 *     * Order.State.CANCELED
	 *     * Order.State.PARTIALLY_FILLED
	 *     * Order.State.FILLED
	 * </pre>
	 * 
	 * @return
	 */
	public State getState() {

		return __state;
	}

	/**
	 * 
	 * @param newState
	 */
	public void setState(State newState) {

		__state = newState;
	}

	/**
	 * Returns the instrument identifier.
	 * 
	 * @return
	 */
	public String getInstrument() {

		return __instrument;
	}

	/**
	 * Returns the quantity.
	 * 
	 * @return
	 */
	public double getQuantity() {

		return __quantity;
	}

	/**
	 * Returns the number of shares that have been executed.
	 * 
	 * @return
	 */
	public double getFilled() {

		return __filled;
	}

	/**
	 * Returns the number of shares still outstanding.
	 * 
	 * @return
	 */
	public double getRemaining() {

		return __instrumentTraits.roundQuantity(__quantity - __filled);
	}

	/**
	 * Returns the average price of the shares that have been executed, or None if nothing has been
	 * filled.
	 * 
	 * @return
	 */
	public double getAvgFillPrice() {

		return __avgFillPrice;
	}

	/**
	 * Returns True if the order is good till canceled.
	 * 
	 * @return
	 */
	public boolean getGoodTillCanceled() {

		return __goodTillCanceled;
	}

	/**
	 * Sets if the order should be good till canceled.<br />
	 * Orders that are not filled by the time the session closes will be automatically canceled if they
	 * were not set as good till canceled
	 * 
	 * @param goodTillCanceled True if the order should be good till canceled.
	 * @throws Exception
	 */
	public void setGoodTillCanceled(boolean goodTillCanceled) throws Exception {

		if (__state != State.INITIAL) {
			throw new Exception("The order has already been submitted");
		}
		__goodTillCanceled = goodTillCanceled;
	}

	/**
	 * 
	 * @return
	 */
	public double getCommissions() {

		return __commissions;
	}

	/**
	 * Returns True if the order should be completely filled or else canceled.
	 * 
	 * @return
	 */
	public boolean getAllOrNone() {

		return __allOrNone;
	}

	/**
	 * <pre>
	 * Sets the All-Or-None property for this order.
	 * 
	 * .. note:: This can't be changed once the order is submitted.
	 * </pre>
	 * 
	 * @param allOrNone True if the order should be completely filled.
	 * @throws Exception
	 */
	public void setAllOrNone(boolean allOrNone) throws Exception {

		if (__state != State.INITIAL) {
			throw new Exception("The order has already been submitted");
		}
		__allOrNone = allOrNone;
	}

	/**
	 * Returns the last execution information for this order, or None if nothing has been filled so far.
	 * This will be different every time an order, or part of it, gets filled.
	 * 
	 * @return
	 */
	public OrderExecutionInfo getExecutionInfo() {

		return __executionInfo;
	}

	/**
	 * 
	 * @param orderExecutionInfo
	 * @throws Exception
	 */
	public void addExecutionInfo(OrderExecutionInfo orderExecutionInfo) throws Exception {

		if (orderExecutionInfo.getQuantity() > getRemaining()) {
			throw new Exception(String.format("Invalid fill size. %s remaining and %s filled", getRemaining(),
			        orderExecutionInfo.getQuantity()));
		}

		if (Double.isNaN(__avgFillPrice)) {
			__avgFillPrice = orderExecutionInfo.getPrice();
		} else {
			__avgFillPrice = (__avgFillPrice * __filled
			        + orderExecutionInfo.getPrice() * orderExecutionInfo.getQuantity())
			        / (__filled + orderExecutionInfo.getQuantity());
		}

		__executionInfo = orderExecutionInfo;
		__filled = getInstrumentTraits().roundQuantity(__filled + orderExecutionInfo.getQuantity());
		__commissions += orderExecutionInfo.getCommission();

		if (DoubleMath.fuzzyEquals(getRemaining(), 0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)) {
			switchState(State.FILLED);
		} else {
			assert !__allOrNone;
			switchState(State.PARTIALLY_FILLED);
		}
	}

	/**
	 * 
	 * @param newState
	 * @throws Exception
	 */
	public void switchState(State newState) throws Exception {

		List<State> validTransitions = Order.VALID_TRANSITIONS.getOrDefault(__state, Arrays.asList());
		if (!validTransitions.contains(newState)) {
			throw new Exception(String.format("Invalid order state transition from %s to %s", __state, newState));
		} else {
			__state = newState;
		}
	}

	/**
	 * Returns True if the order is active.
	 * 
	 * @return
	 */
	public boolean isActive() {

		return !Arrays.asList(State.CANCELED, State.FILLED).contains(__state);
	}

	/**
	 * Returns True if the order state is Order.State.INITIAL.
	 * 
	 * @return
	 */
	public boolean isInitial() {

		return __state == State.INITIAL;
	}

	/**
	 * Returns True if the order state is Order.State.SUBMITTED.
	 * 
	 * @return
	 */
	public boolean isSubmitted() {

		return __state == State.SUBMITTED;
	}

	/**
	 * Returns True if the order state is Order.State.ACCEPTED.
	 * 
	 * @return
	 */
	public boolean isAccepted() {

		return __state == State.ACCEPTED;
	}

	/**
	 * Returns True if the order state is Order.State.CANCELED.
	 * 
	 * @return
	 */
	public boolean isCanceled() {

		return __state == State.CANCELED;
	}

	/**
	 * Returns True if the order state is Order.State.PARTIALLY_FILLED.
	 * 
	 * @return
	 */
	public boolean isPartiallyFilled() {

		return __state == State.PARTIALLY_FILLED;
	}

	/**
	 * Returns True if the order state is Order.State.FILLED.
	 * 
	 * @return
	 */
	public boolean isFilled() {

		return __state == State.FILLED;
	}

	/**
	 * Returns True if this is a BUY or BUY_TO_COVER order.
	 * 
	 * @return
	 */
	public boolean isBuy() {

		return Arrays.asList(Action.BUY, Action.BUY_TO_COVER).contains(__action);
	}

	/**
	 * Returns True if this is a SELL or SELL_SHORT order.
	 * 
	 * @return
	 */
	public boolean isSell() {

		return Arrays.asList(Action.SELL, Action.SELL_SHORT).contains(__action);
	}
}
